/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#ifndef PLELF_BASE_H
#define PLELF_BASE_H

#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <tools/PLRadixTree.h>
#include <tools/PLLinkedList.h>
#include <kernel/PLGlobal.h>
#include <elf/plelfstddef.h>
#include <elf/plelfstringtable.h>
#if defined(KERNEL)
//#include <machine/machine.h>
#endif

#if defined(PLELF32BIT)
#include <elf/plelfsymbol32.h>
#include <elf/plelfheader32.h>
#include <elf/plelfsectionheader32.h>
#include <elf/plelfsymboltable32.h>
#include <elf/plelfrelocation32.h>
#include <elf/plelfrelocationa32.h>
#include <elf/plelfapplicationheader32.h>

namespace PLSDK {
	
    typedef Elf32_Addr                  Elf_Addr;
    typedef Elf32_Off                   Elf_Off;
    typedef Elf32_Word                  Elf_Index;
	typedef PLELFHeader32				PLELFHeader;
	typedef PLELFSectionHeader32		PLELFSectionHeader;
	typedef PLELFSymbolTable32			PLELFSymbolTable;
	typedef PLELFRelocation32			PLELFRelocation;
	typedef PLELFRelocationA32			PLELFRelocationA;
	typedef PLELFApplicationHeader32    PLELFApplicationHeader;
    typedef PLELFSymbol32               PLELFSymbol;
    typedef PLElf32                     PLELF;

}
#elif defined(PLELF64BIT)
#include <elf/plelfsymbol64.h>
#include <elf/plelfheader64.h>
#include <elf/plelfsectionheader64.h>
#include <elf/plelfsymboltable64.h>
#include <elf/plelfrelocation64.h>
#include <elf/plelfrelocationa64.h>
#include <elf/plelfapplicationheader64.h>

namespace PLSDK {
	
    typedef Elf64_Addr                  Elf_Addr;
    typedef Elf64_Off                   Elf_Off;
    typedef Elf64_Word                  Elf_Index;
	typedef PLELFHeader64				PLELFHeader;
	typedef PLELFSectionHeader64		PLELFSectionHeader;
	typedef PLELFSymbolTable64			PLELFSymbolTable;
	typedef PLELFRelocation64			PLELFRelocation;
	typedef PLELFRelocationA64			PLELFRelocationA;
	typedef PLELFApplicationHeader64    PLELFApplicationHeader;
    typedef PLELFSymbol64               PLELFSymbol;
    typedef PLElf64                     PLELF;
	
}
#else
#  error "No bit length set as define."
#endif

namespace PLSDK {

    /**
     * The class that implement the 32 bit and 64 bit read implementation.
     */
    class PLElfBase {
        friend class PLElf32;
        friend class PLElf64;
        friend class PLMachine;
    public:
        /**
         * Error codes for the elf creation.
         */
        enum ElfCreateError {
            /**
             * No error
             */
            ElfCreateErrorNone							= 0,
            /**
             * The magic ident was not correct.
             */
            ElfCreateErrorIdentCheckFailed              = 1,
            /**
             * The class of the file was not correct.
             * You have a 32 bit reader and you want to open a 64 bit file or
             * you have a 64 bit reader and you want to open a 32 bit file.
             */
            ElfClassNotCorrect							= 2,
            /**
             * The symbol table was not found in the file.
             */
            ElfSymbolTableNotFound						= 3,
            /**
             * The string table was not found in the file.
             */
            ElfStringTableNotFound						= 4
        };
    public:
        /**
         * Constructor.
         */
        inline PLElfBase();
        /**
         * Create the elf object.
         *
         * @param buffer The pointer to the elf file data.
         * @param size The size of the data.
         * @return The error code from the elf creation.
         */
        inline PLElfBase::ElfCreateError create(uint8_t *buffer, size_t size);
        /**
         * Create the elf object.
         *
         * @param sectionHeaders The section header tables.
         * @param numberSectionHeaders The number of section header tables.
         * @param sectionHeaderStringTable The string tables for the sections headers.
         * @param base The start address of the elf module.
         * @param useAddrss Use the address or the offset to get the symbole table, ...
         * @return The error code from the elf creation.
         */
        inline PLElfBase::ElfCreateError create(PLELFSectionHeader *sectionHeaders, size_t numberSectionHeaders, PLELFStringTable *sectionHeaderStringTable, uintptr_t base = 0, bool useAddrss = true);
        /**
         * Relocate the symbols from the relocation table.
         *
         * @param buffer The buffer of the elf application.
         * @param size The size of the data.
         * @return True if all was successfully.
         */
        inline bool relocate(uint8_t *buffer, uintptr_t size);
        /**
         * Lookup for a symbol in the elf header.
         *
         * @param name The symbol to look for.
         * @return The address of the symbol.
         */
        uintptr_t lookup(const char *name);
    private:
        inline void insertSymbol(const char *pStr, PLELFSymbolTable *symbolTable);
        inline bool applyRelocation(PLELFRelocation rel, PLELFSectionHeader *pSh, PLELFSymbolTable *pSymtab = 0, uintptr_t loadBase = 0);
        inline bool applyRelocation(PLELFRelocationA rel, PLELFSectionHeader *pSh, PLELFSymbolTable *pSymtab = 0, uintptr_t loadBase = 0);
        PLElfIdentification::EIClass     m_elfClass;
        PLELFHeaderEnums::EMachine       m_machine;
        size_t                           m_numberSectionHeaders;
        PLELFSectionHeader              *m_sectionHeaders;
        PLELFSectionHeader              *m_bssTable;
        PLELFSectionHeader              *m_commentTable;
        PLELFSectionHeader              *m_dataTable;
        PLELFSectionHeader              *m_data1Table;
        PLELFSectionHeader              *m_rodataTable;
        PLELFSectionHeader              *m_rodata1Table;
        PLELFSectionHeader              *m_debugAbbrev;
        PLELFSectionHeader              *m_debugInfo;
        PLELFSectionHeader              *m_debugLine;
        PLELFSectionHeader              *m_debugLoc;
        PLELFSectionHeader              *m_debugPubNames;
        PLELFSectionHeader              *m_debugAranges;
        PLELFSectionHeader              *m_debugString;
        PLELFSectionHeader              *m_debugMacInfo;
        PLELFSectionHeader              *m_debugRanges;
        PLELFSectionHeader              *m_dynamic;
        PLELFSectionHeader              *m_dynamicString;
        PLELFSectionHeader              *m_dynamicSymbol;
        PLELFSectionHeader              *m_globalOffsetTable;
        PLELFSectionHeader              *m_symbolHashtable;
        PLELFSectionHeader              *m_programInterpreter;
        PLELFSectionHeader              *m_lineNumber;
        PLELFSectionHeader              *m_note;
        PLELFSectionHeader              *m_procedureLinkageTable;
        PLELFSectionHeader              *m_stringTableHeader;
        PLELFSectionHeader              *m_symbolTableHeader;
        PLELFSectionHeader              *m_symbolTableIndexArray;
        PLELFSectionHeader              *m_textTable;
        PLELFStringTable                *m_sectionHeaderStringTable;
        PLELFStringTable                *m_stringTable;
        PLELFSymbolTable                *m_symbolTable;
        size_t                           m_symbolTableSize;
        typedef PLLinkedList<PLELFSymbol *> SymbolList;
        PLRadixTree<SymbolList *>        m_symboleTree;
        PLDISABLE_COPY(PLElfBase)
    };

    PLElfBase::PLElfBase() : m_sectionHeaders(0), m_sectionHeaderStringTable(0), m_stringTable(0) {
    }

    PLElfBase::ElfCreateError PLElfBase::create(uint8_t *buffer, size_t size) {
        PLUNUSED(size)
        PLELFHeader *header = reinterpret_cast<PLELFHeader *>(buffer);
        PLElfIdentification *ident = header->identification();
        if(!ident->checkHeader()) {
            return PLElfBase::ElfCreateErrorIdentCheckFailed;
        }
        m_elfClass = ident->fileClass();
        m_machine  = header->machine();
        size_t numberSectionHeaders = header->e_shnum;
        PLELFSectionHeader *sectionHeaders = (PLELFSectionHeader *)(buffer + header->e_shoff);
        PLELFSectionHeader *sectionHeaderStringTable = &sectionHeaders[header->e_shstrndx];
        PLElfBase::ElfCreateError ret = PLElfBase::ElfCreateErrorNone;
        ret = create(sectionHeaders, numberSectionHeaders, new PLELFStringTable(sectionHeaderStringTable->sh_offset + (Elf_Addr)buffer, sectionHeaderStringTable->sh_size), (uintptr_t)buffer, false);
        if(ret == PLElfBase::ElfCreateErrorNone) {
            // TODO implement application header table.
        }
        return ret;
    }

    PLElfBase::ElfCreateError PLElfBase::create(PLELFSectionHeader *sectionHeaders, size_t numberSectionHeaders, PLELFStringTable *sectionHeaderStringTable, uintptr_t base, bool useAddrss) {
        PLElfBase::ElfCreateError ret = PLElfBase::ElfCreateErrorNone;
        m_sectionHeaders = sectionHeaders;
        m_numberSectionHeaders = numberSectionHeaders;
        m_sectionHeaderStringTable = sectionHeaderStringTable;
        m_bssTable = m_commentTable = m_dataTable = m_data1Table = m_rodataTable = m_rodata1Table = m_debugAbbrev = 0;
        m_debugInfo = m_debugLine = m_debugLoc = m_debugPubNames = m_debugAranges = m_debugString = m_debugMacInfo = 0;
        m_debugRanges = m_dynamic = m_dynamicString = m_dynamicSymbol = m_globalOffsetTable = m_symbolHashtable = 0;
        m_programInterpreter = m_lineNumber = m_note = m_procedureLinkageTable = m_stringTableHeader = m_symbolTableHeader = 0;
        m_textTable = m_symbolTableIndexArray = 0;
        for(size_t i = 0 ; i < numberSectionHeaders ; i++) {
            const char *pStr = m_sectionHeaderStringTable->stringFromIndex(m_sectionHeaders[i].sh_name);
            if(strcmp(pStr, PLELFSectionHeader::SHNamesTextTable) == 0) {
                m_textTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesRoDataTable) == 0) {
                m_rodataTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesRoData1Table) == 0) {
                m_rodata1Table = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDataTable) == 0) {
                m_dataTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesData1Table) == 0) {
                m_data1Table = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesBssTable) == 0) {
                m_bssTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesSymbolTable) == 0) {
                m_symbolTableHeader = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesSymbolTableIndexArray) == 0) {
                m_symbolTableIndexArray = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesStringTable) == 0) {
                m_stringTableHeader = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesCommentTable) == 0) {
                m_commentTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDynamic) == 0) {
                m_dynamic = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDynamicString) == 0) {
                m_dynamicString = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDynamicSymbol) == 0) {
                m_dynamicSymbol = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesGlobalOffsetTable) == 0) {
                m_globalOffsetTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesSymbolHashtable) == 0) {
                m_symbolHashtable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesProgramInterpreter) == 0) {
                m_programInterpreter = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesNote) == 0) {
                m_note = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesProcedureLinkageTable) == 0) {
                m_procedureLinkageTable = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesLineNumber) == 0) {
                m_lineNumber = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugAbbrev) == 0) {
                m_debugAbbrev = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugInfo) == 0) {
                m_debugInfo = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugLine) == 0) {
                m_debugLine = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugLoc) == 0) {
                m_debugLoc = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugPubNames) == 0) {
                m_debugPubNames = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugAranges) == 0) {
                m_debugAranges = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugString) == 0) {
                m_debugString = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugMacInfo) == 0) {
                m_debugMacInfo = &m_sectionHeaders[i];
            } else if(strcmp(pStr, PLELFSectionHeader::SHNamesDebugRanges) == 0) {
                m_debugRanges = &m_sectionHeaders[i];
            }
        }
        if(!m_symbolTableHeader) {
            ret = PLElfBase::ElfSymbolTableNotFound;
            printf("Symboltable not found.");
        } else {
            if(useAddrss) {
                m_symbolTable = reinterpret_cast<PLELFSymbolTable *>(m_symbolTableHeader->sh_addr + base);
            } else {
                m_symbolTable = reinterpret_cast<PLELFSymbolTable *>(m_symbolTableHeader->sh_offset + base);
            }
            m_symbolTableSize = m_symbolTableHeader->sh_size;
        }
        if(!m_stringTableHeader) {
            ret = PLElfBase::ElfStringTableNotFound;
            printf("Stringtable not found.");
        } else {
            if(useAddrss) {
                m_stringTable = new PLELFStringTable(m_stringTableHeader->sh_addr + base, m_stringTableHeader->sh_size);
            } else {
                m_stringTable = new PLELFStringTable(m_stringTableHeader->sh_offset + base, m_stringTableHeader->sh_size);
            }
        }
        if(ret != PLElfBase::ElfSymbolTableNotFound && ret != PLElfBase::ElfStringTableNotFound) {
            PLELFSectionHeader *infoTypeSectionHeader = 0;
            for(size_t i = 0 ; i < m_symbolTableSize / sizeof(PLELFSymbolTable) ; i++) {
                const char *pStr = 0;
                if(m_symbolTable->infoSymbolTypes() == PLELFSymbolTableEnum::STInfoTypesSection) {
                    infoTypeSectionHeader = &m_sectionHeaders[m_symbolTable->st_shndx];
                    if(infoTypeSectionHeader && infoTypeSectionHeader->flags() != PLELFSectionHeaderEnums::SHFlagsAlloc) {
                        m_symbolTable++;
                        continue;
                    }
                    pStr = m_stringTable->stringFromIndex(infoTypeSectionHeader->sh_name);
                } else if(m_symbolTable->infoSymbolTypes() != PLELFSymbolTableEnum::STInfoTypesFile) {
                    pStr = m_stringTable->stringFromIndex(m_symbolTable->st_name);
                }
                if(pStr && (*pStr != '\0')) {
                    insertSymbol(pStr, m_symbolTable);
                }
                m_symbolTable++;
            }
        }
        return ret;
    }

    void PLElfBase::insertSymbol(const char *pStr, PLELFSymbolTable *symbolTable) {
        PLELFSymbolTableEnum::STInfoBinding binding = symbolTable->infoSymbolBinding();
        SymbolList *pList = m_symboleTree.lookup(pStr);
        if(!pList) {
            pList = new SymbolList();
            m_symboleTree.insert(pStr, pList);
        }
        if(pList->count() > 0) {
            PLELFSymbol *symb;
            PLLinkedListIterator<PLELFSymbol *> it = pList->iterator();
            while(it != pList->lastNode()) {
                symb = it.next();
                if(symb->binding() == binding) {
                    return;
                }
            }
        }
        pList->append(new PLELFSymbol(binding, symbolTable->st_value));
    }

    bool PLElfBase::relocate(uint8_t *buffer, uintptr_t size) {
        PLELFSectionHeader *pLink;
        PLELFSectionHeader *pSh;
        for(size_t i = 0 ; i < m_numberSectionHeaders ; i++) {
            pSh = &m_sectionHeaders[i];
            if(pSh->type() != PLELFSectionHeaderEnums::SHTypeRelocation && pSh->type() != PLELFSectionHeaderEnums::SHTypeRelocationA) {
                continue;
            }
            pLink = &m_sectionHeaders[pSh->sh_info];
            const char *pStr = m_sectionHeaderStringTable->stringFromIndex(pLink->sh_name);
            if(!strcmp(pStr, ".modinfo")) {
                continue;
            }
            if(pSh->type() == PLELFSectionHeaderEnums::SHTypeRelocation) {
                for(PLELFRelocation *pRel = reinterpret_cast<PLELFRelocation *> (&buffer[pSh->sh_offset]);
                    pRel < reinterpret_cast<PLELFRelocation *>(&buffer[pSh->sh_offset + pSh->sh_size]);
                    pRel++) {
                    if(!applyRelocation(*pRel, pLink)) {
                        return false;
                    }
                }
            } else if(pSh->type() == PLELFSectionHeaderEnums::SHTypeRelocationA) {
                for(PLELFRelocationA *pRel = reinterpret_cast<PLELFRelocationA *>(&buffer[pSh->sh_offset]);
                    pRel < reinterpret_cast<PLELFRelocationA *>(&buffer[pSh->sh_offset + pSh->sh_size]);
                    pRel++) {
                    if(!applyRelocation(*pRel, pLink)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    #define R_386_NONE     0
    #define R_386_32       1
    #define R_386_PC32     2
    #define R_386_GOT32    3
    #define R_386_PLT32    4
    #define R_386_COPY     5
    #define R_386_GLOB_DAT 6
    #define R_386_JMP_SLOT 7
    #define R_386_RELATIVE 8
    #define R_386_GOTOFF   9
    #define R_386_GOTPC    10

    bool PLElfBase::applyRelocation(PLELFRelocation rel, PLELFSectionHeader *pSh, PLELFSymbolTable *pSymtab, uintptr_t loadBase) {
        if(m_machine == PLELFHeaderEnums::EMachine_386) {
            if(pSh && pSh->sh_addr == 0) {
                return true;
            }
            uintptr_t address = ((pSh) ? pSh->sh_addr : loadBase) + rel.r_offset;
            uintptr_t A = *reinterpret_cast<uintptr_t *>(address);
            uintptr_t P = address;
            uintptr_t S = 0;
            PLELFSymbolTable *pSymbols = 0;
            if(!m_dynamicSymbol) {
                pSymbols = reinterpret_cast<PLELFSymbolTable *>(m_symbolTable);
            } else {
                printf("TODO");
// TODO
//                pSymbols = m_dynamicSymbol;
            }
            PLELFStringTable *pStringTable;
            if(!m_dynamicString) {
                pStringTable = new PLELFStringTable(m_stringTableHeader->sh_offset, m_stringTableHeader->sh_size);
            } else {
                pStringTable = new PLELFStringTable(m_dynamicString->sh_offset, m_dynamicString->sh_size);
            }
            PLELFSymbolTable relSymbol;
            if(pSymbols && (relSymbol = pSymbols[rel.infoSymbol()]).infoSymbolTypes() == PLELFSymbolTable::STInfoTypesSection) {
                S = (&m_sectionHeaders[relSymbol.st_shndx])->sh_addr;
            } else if(rel.infoType() != R_386_RELATIVE) {
                const char *pStr = pStringTable->stringFromIndex(relSymbol.st_name);
                if(pSymtab == 0) {
#if defined(KERNEL)
                    printf("TODO");
                    // pSymtab = PLMachine::instance().kernelElfInformation()->m_symbolTable;
#else
                    printf("TODO");
                    // TODO
#endif
                }
                if(rel.infoType() == R_386_COPY) {
                    printf("TODO");
                    // TODO
                }
                S = lookup(pStr);
                if(S == 0 && strlen(pStr)) {
                    printf("Relocation failed for symbol \" %s \" (relocation=%d)", pStr, rel.infoType());
                } else if(S == ~0UL) {
                    printf("Weak relocation == 0 [undefined] for \" %s \".", pStr);
                }
            }
            if(S == 0 && (rel.infoType() != R_386_RELATIVE)) {
                return false;
            }
            if(S == ~0UL) {
                S = 0;
            }
            uintptr_t B = loadBase;
            uintptr_t result = A;
            switch(rel.infoType()) {
                case R_386_NONE:
                    break;
                case R_386_32:
                    result = S + A;
                    break;
                case R_386_PC32:
                    result = S + A - P;
                    break;
                case R_386_JMP_SLOT:
                case R_386_GLOB_DAT:
                    result = S;
                    break;
                case R_386_COPY:
                    result = *reinterpret_cast<uintptr_t *>(S);
                    break;
                case R_386_RELATIVE:
                    result = B + A;
                    break;
                default:
                    printf("Relocation not supported: %d", rel.infoType());
            }
            uintptr_t *pResult = reinterpret_cast<uintptr_t *>(address);
            *pResult = result;
            return true;
        }
        printf("Error Relocation not implemented for this architecture");
        return false;
    }

    bool PLElfBase::applyRelocation(PLELFRelocationA rel, PLELFSectionHeader *pSh, PLELFSymbolTable *pSymtab, uintptr_t loadBase) {
        PLUNUSED(rel)
        PLUNUSED(pSh)
        PLUNUSED(pSymtab)
        PLUNUSED(loadBase)
        printf("Error Relocation not implemented for this architecture");
        return false;
    }

}

#endif
